﻿using System;
using System.IO;
using System.Linq;
using System.Xml;
using System.Reflection;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ComponentModel.Composition;

using SharpGL.SceneGraph;

namespace SharpGL.Serialization.SharpGL
{
    class SceneDataContractResolver : DataContractResolver
    {
        private Dictionary<string, Type> typesByNamespace = null;
        private Dictionary<Type, string> typesByType = null;

        public SceneDataContractResolver()
        {
            typesByNamespace = new Dictionary<string, Type>();
            typesByType = new Dictionary<Type, string>();
            ConstructTypeMap();
        }

        private void ConstructTypeMap()
        {
            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly assembly in assemblies)
            {
                var availableTypes = from t in assembly.GetTypes()
                                     where t.GetCustomAttributes(typeof(DataContractAttribute), true).Any()
                                     select t;

                foreach (Type type in availableTypes)
                {
                    object[] attributes = type.GetCustomAttributes(
                        typeof(DataContractAttribute), true);

                    if (attributes.Length > 0)
                    {
                        DataContractAttribute taskContent =
                            attributes[0] as DataContractAttribute;

                        string ns = taskContent.Namespace + type.Name;

                        typesByNamespace.Add(ns, type);
                        typesByType.Add(type, ns);
                    }
                }
            }

        }

        // Serialization
        public override bool TryResolveType(Type type, Type declaredType,
            DataContractResolver knownTypeResolver,
            out System.Xml.XmlDictionaryString typeName,
            out System.Xml.XmlDictionaryString typeNamespace)
        {
            Type[] genericTypes = type.GetGenericArguments();

            string ns = string.Empty;
            Type genericType = null;

            /*foreach (Type genType in genericTypes)
            {
                if (typesByType.ContainsKey(genType) == true)
                {
                    typesByType.TryGetValue(genType, out ns);
                    genericType = genType;
                    break;
                }
            }*/

            //string ns = string.Empty;
            //Type genericType = null;

            if (typesByType.ContainsKey(type) == true)
            {
                typesByType.TryGetValue(type, out ns);
                genericType = type;
            }
            
            if (string.IsNullOrEmpty(ns) == false)
            {
                System.Xml.XmlDictionary dictionary = new System.Xml.XmlDictionary();
                typeName = dictionary.Add(genericType.Name);
                typeNamespace = dictionary.Add(ns);

                return true;
            }
            else
            {
                return knownTypeResolver.TryResolveType(
                    type, declaredType, null, out typeName, out typeNamespace);
            }
        }

        // Deserialization
        public override Type ResolveName(string typeName, string typeNamespace,
            Type declaredType, DataContractResolver knownTypeResolver)
        {
            Type type = null;
            typesByNamespace.TryGetValue(typeNamespace, out type);

            if (type != null)
            {
                Type genType = typeof(Scene);
                //Type genericType = genType.MakeGenericType(type);
                return type;
            }
            else
            {
                // Defer to the known type resolver
                return knownTypeResolver.ResolveName(
                    typeName, typeNamespace, null, knownTypeResolver);
            }
        }
    }
    
    /// <summary>
    /// The SharpGL XML format.
    /// </summary>
    [Export(typeof(IFileFormat))]
    public class SharpGLXmlFormat : IFileFormat
    {
        /// <summary>
        /// Load the data from the specified file fileStream. The data
        /// should be loaded into a scene object. Also, for consistency
        /// the ObjectLoaded event should be fired every time an object
        /// (such as a polygon or material) is loaded.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <returns>
        /// The scene or null if loading failed.
        /// </returns>
        public SceneGraph.Scene LoadData(string path)
        {
            Scene scene = null;

            try
            {
                using (FileStream fileStream = new FileStream(path, FileMode.Open))
                {
                    XmlDictionaryReaderQuotas readerQuotas = new XmlDictionaryReaderQuotas();
                    readerQuotas.MaxArrayLength = int.MaxValue;
                    readerQuotas.MaxDepth = 1024;
                    using (XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fileStream, readerQuotas))
                    {
                        DataContractSerializer serializer = new DataContractSerializer(typeof(Scene), null, int.MaxValue, true, true, null, new SceneDataContractResolver());
                        // Deserialize the data and read it from the instance.
                        scene = (Scene)serializer.ReadObject(reader, true);
                    }
                }
            }
            catch (Exception e)
            {
                throw new Exception("Failed to load Scene from XML data.", e);
            }

            return scene;
        }

        /// <summary>
        /// Saves the scene to the specified fileStream.
        /// </summary>
        /// <param name="scene">The scene.</param>
        /// <param name="path">The path.</param>
        /// <returns>
        /// True if saved correctly.
        /// </returns>
        public bool SaveData(Scene scene, string path)
        {
            try
            {
                using (FileStream fileStream = new FileStream(path, FileMode.Create))
                {
                    using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(fileStream))
                    {
                        DataContractSerializer serializer = new DataContractSerializer(typeof(Scene), null, int.MaxValue, true, true, null, new SceneDataContractResolver());
                        serializer.WriteObject(writer, scene);
                    }
                }
            }
            catch (Exception e)
            {
                throw new Exception("Failed to save Scene from XML data.", e);
            }
               
            return true;
        }
        
        /// <summary>
        /// This property returns an array of file types that can be used with this
        /// format, e.g the CaligariFormat would return "cob", "scn".
        /// </summary>
        public string[] FileTypes
        {
            get { return new string[] { "sglsx" }; }
        }

        /// <summary>
        /// This gets a filter suitable for a file open/save dialog, e.g
        /// "Caligari trueSpace Files (*.cob, *.scn)|*.cob;*.scn".
        /// </summary>
        public string Filter
        {
            get { return "SharpGL XML Scenes (*.sglsx)|*.sglsx"; }
        }
    }
}
